const CFG = require('../config');
const FUN = require('../comm/func');
const tbPolicy = require('../model/tb_policy');
const tbUserId = require('../model/tb_user_id');
const dbHelper = require('../comm/db-helper');
const rCache = require('../model/redis_cache');
const timLib = require('../comm/time-lib');
const request = require('request');
const fs = require('fs');
const path = require('path');

// 策略文件存放路径, 适配不同OS
const PCY_DIR = path.join(process.cwd(), 'policy');

// 动态策略信息表
const PCY_MAP = new Map(); // 键名是id号
// en:1,0 表示是否允许运行
// msGap: 到期时间，0为停用
// lastExpire: 上次到期时间

// 策略订阅信息表
const PCY_TOPICS_MAP = new Map(); // 键名是主题
// 键值是数组，保存策略名称policy，id
let NeedSubTopics = []; // 需订阅的主题列表
let NeedCancleTopics = []; // 需取消订阅的主题列表

module.exports = class policyMan
{
  static fetchNum = 30; // 每次从数据库获取的条目数
  static mqConn = null; // mqtt客户端连接对象
  static mqPub = null;
  static mode = 0; // 0正常，1调试

  // 拼接redis缓存节点，助手函数
  static rdNode(dConf, sub = null)
  {
    const myNode = `${dConf.policy}:${dConf.hwid}.${dConf.policy_id}`;
    if (sub) return `${myNode}:${sub}`;
    return myNode;
  }

  // 拼接策略参数节点，助手函数
  static policyNode(dConf)
  {
    return `${rCache.N_Policy}:${dConf.hwid}:${dConf.policy_id}`;
  }

  // 初始化基本的redis存储参数，助手函数
  // policy 来自数据库的记录，附加了user_auth,user_level字段
  static baseParam(policy)
  {
    let obj = {};
    obj.uid = policy.uid;
    obj.hwid = policy.master;
    obj.policy_id = policy.id;
    obj.policy_name = policy.name;
    obj.policy = policy.type;
    obj.table = policy.table ? policy.table : '';

    if (policy.table) { // 为用户创建数据表
      dbHelper.createTab(policy.table, policy.uid);
    }

    return obj;
  }

  // 停止处理此策略，助手函数
  // id: 策略id
  static disablePolicy(id)
  {
    let pInfo = PCY_MAP.get(id);
    if (pInfo) {
      pInfo.en = 0;
      PCY_MAP.set(id, pInfo);
      console.log('[%d] policy disabled.', id);
    }
  }

  // 提取record中的options选项为对象，附加用户级别信息到record
  static async getOptionsObj(rcd)
  {
    let options = JSON.parse(rcd.options); // 将字符串转换为对象
    for (let key in options) { // 由于前端存储options时进行了编码，此处需要解码
      options[key] = FUN.Base64ToString(options[key]);
    }
    let user = await tbUserId.getFields(rcd.uid, 'level,auth');
    rcd._user_level = user.level; // 附加用户级别及管理权限信息
    rcd._user_auth = user.auth;
    return options;
  }

  // 注册订阅主题
  static subMqttTopic(topic, policy, id)
  {
    let pcyArr = PCY_TOPICS_MAP.get(topic); // 每个主题必须唯一记录
    if (pcyArr) {
      pcyArr.push({policy:policy, id:id});
      PCY_TOPICS_MAP.set(topic, pcyArr);
    } else {
      PCY_TOPICS_MAP.set(topic, [{policy:policy, id:id}]);
      NeedSubTopics.push(topic); // 第一次要注册
    }
  }

  // 取消订阅主题
  static cancleMqttTopic(topic, policy, id)
  {
    let pcyArr = PCY_TOPICS_MAP.get(topic); // 每个主题必须唯一记录
    if (pcyArr) {
      for (let i = pcyArr.length - 1; i >= 0; i--) {
        if (pcyArr[i].policy == policy && pcyArr[i].id == id) {
          pcyArr.splice(i, 1); // 使用splice()函数在索引位置上删除元素
        }
      }
      if (pcyArr.length == 0) { // 没有关注者了就删除并取消订阅
        PCY_TOPICS_MAP.delete(topic);
        NeedCancleTopics.push(topic);
      } else {
        PCY_TOPICS_MAP.set(topic, pcyArr);
      }
    } else {
      NeedCancleTopics.push(topic);
    }
  }

  // 本管理器初始化
  static async Init(mode)
  {
    if (mode) this.mode = 1;

    // await rCache.delNode(rCache.N_Policy); // 清空所有策略缓存，不能这样做！！！
    PCY_MAP.clear();
    PCY_TOPICS_MAP.clear();
    NeedSubTopics = [];
    let affectedRows = await tbPolicy.deInitAll(this.mode); // 初始化标志init还原为0
    let initTotal = affectedRows;

    // console.log(path.resolve(__dirname,'../policy'),process.cwd());
    console.log('[PMR] Manger Init', initTotal, this.mode?'[Debug Mode]':'');
    setInterval(this.SecTimerPoll, 1000); // 每秒定时器

    await this.FlushDeleteItem();
    await this.FlushNewItem();
  }

  // 每秒钟一次的定时器
  static SecTimerPoll()
  {
    let nowStamp = timLib.NowStamp();
    PCY_MAP.forEach((pInfo, policy_id)=>{
      if (pInfo.msGap && pInfo.lastExpire+pInfo.msGap <= nowStamp) {
        //获取rCache信息
        rCache.getAll(rCache.N_Policy,pInfo.master+':'+policy_id).then(optsCache=>{
          if (optsCache && optsCache.policy) {
            if (pInfo.en) {
              let jsPath = path.join(PCY_DIR, optsCache.policy+'.js');
              if (require(jsPath).SecInter) {
                let res = require(jsPath).SecInter(optsCache, nowStamp);
                if (res == false) pInfo.msGap = 0;
              }
            }
            pInfo.lastExpire = nowStamp;
            PCY_MAP.set(policy_id,pInfo);//更新时间
          }
        }) // end rCache getAll
      } // end if
    })
  }

  // 前端接口发来更新命令
  static async FlushDeleteItem()
  {
    let count = 0;
    while (1) { // 处理被删除的策略
      let Rcds = await tbPolicy.fetchDel(this.fetchNum, this.mode); // 分批取出del=1的策略数据
      if (Rcds.length == 0) break;
      for (let rcd of Rcds) {
        let options = await this.getOptionsObj(rcd);
        this.PcyItem_Destroy(rcd, options); // 销毁缓存
      }
      let firstId = Rcds[0].id;
      let lastId = Rcds.pop().id;
      let affectedRows = await tbPolicy.deleteByIdSection(firstId, lastId, this.mode); // 删除del=1的策略数据记录
      count += affectedRows;
    } // end while 1
    console.log('[PMR] Policy Destroy Item', count, this.mode?'[Debug Mode]':'');
  }

  // 前端接口发来更新命令
  static async FlushNewItem()
  {
    let count = 0;
    while (1) { // 处理新的策略
      let Rcds = await tbPolicy.fetch(this.fetchNum, this.mode); // 分批取出策略数据
      if (Rcds.length <= 0) break;
      for (let rcd of Rcds) {
        let options = await this.getOptionsObj(rcd);
        this.PcyItem_Init(rcd, options); // 处理选项
      }
      let firstId = Rcds[0].id;
      let lastId = Rcds.pop().id;
      let affectedRows = await tbPolicy.initById(firstId, lastId, this.mode); // 设置初始化标志
      count += affectedRows;
    } // end while 1
    console.log('[PMR] Policy New Items', count, this.mode?'[Debug Mode]':'');
  }

  // 前端接口发来编辑后待更新命令
  static async FlushResetItem()
  {
    let count = 0;
    while (1) { // 先执行销毁，再执行新建
      let Rcds = await tbPolicy.fetchEdited(this.fetchNum, this.mode); // 分批取出init=2的策略数据
      if (Rcds.length == 0) break;
      for (let rcd of Rcds) {
        let options = await this.getOptionsObj(rcd);
        this.PcyItem_Destroy(rcd, options); // 销毁缓存
      }
      let firstId = Rcds[0].id;
      let lastId = Rcds.pop().id;
      let affectedRows = await tbPolicy.deInitEditedById(firstId, lastId, this.mode); // 删除del=1的策略数据记录
      count += affectedRows;
    } // end while 1
    console.log('[PMR] Policy Reset Item', count, this.mode?'[Debug Mode]':'');
    this.FlushNewItem(); // 销毁后重新初始化
  }

  // 处理单个策略，将数据库策略缓存起来
  // policy 数据表记录，叠加了_user_level，_user_auth
  // opts json选项
  static PcyItem_Init(policy, opts)
  {
    const filter = ['input-alarm']; // 不处理的白名单
    if (filter.includes(policy.type)) return;

    const jsPath = path.join(PCY_DIR, policy.type+'.js'); // 相对本应用的路径
    fs.exists(jsPath, (exists)=>{
      PCY_MAP.set(policy.id, {master:policy.master, en:0}); // 初始化信息
      if (!exists) return console.log('[PMR] err: non-found', jsPath);

      // 调用初始化回调
      let enable = require(jsPath).InitCache(policy, opts, (id,DevId,Info)=>{
        Info.p_init_time = timLib.NowStr(); // 存储当前时间
        rCache.set(rCache.N_Policy, `${DevId}:${id}`, Info); // 供每个策略初始化使用的option缓存执行函数
        rCache.set(rCache.N_PolicyId, `${id}`, Info);
      })

      // 写回信息
      let pInfo = PCY_MAP.get(policy.id);
      if (pInfo && enable != false) {
        pInfo.en = 1;
        PCY_MAP.set(policy.id, pInfo);
      }

      // 处理mq订阅
      if (NeedSubTopics.length) {
        this.mqConn.subscribe(NeedSubTopics, (err) => { if(err)console.log('sub err:',err) });
        outLog('[PMR] subscribe -------->', NeedSubTopics);
        NeedSubTopics = [];
      }
    }) // end exists
  } // end funtion policyCache

  // 处理单个销毁注销的策略
  // policy为数据库记录
  static PcyItem_Destroy(policy, opts)
  {
    const jsPath = path.join(PCY_DIR, policy.type+'.js'); // 相对本应用的路径
    fs.exists(jsPath, (exists)=>{
      PCY_MAP.delete(policy.id); // 删除信息
      if (!exists) {
        return console.log('[PMR] err: non-found', jsPath);
      }

      // 策略退出回调
      if (require(jsPath).Exit) {
        require(jsPath).Exit(policy, opts);
      }

      // 处理mq取消订阅
      if (NeedCancleTopics.length) {
        this.mqConn.unsubscribe(NeedCancleTopics);
        outLog('[PMR] unsubscribe ------X', NeedCancleTopics);
        NeedCancleTopics = [];
      }
    }) // end exists

    rCache.del(rCache.N_Policy, policy.master+':'+policy.id);
    rCache.del(rCache.N_PolicyId, policy.id);
    if (policy.brother) rCache.del(rCache.N_Policy, policy.brother+':'+policy.id);
  }

  // 外部的消息驱动
  static OnStateUpdeted(state)
  {
    console.log(`[PMR] State Updeted >>>>>>>>>> ${state} <<<<<<<<<<`);
    if (state == 'new') this.FlushNewItem(); // 新策略
    else if(state == 'set') this.FlushResetItem(); // 策略被修改
    else if(state == 'del') this.FlushDeleteItem(); // 删除策略
  }

  // 总的mq消息处理，注册到mq客户端的回调函数
  // Path即topic，包含了project，devType，func，devId
  static mqMsgIn(Path, Payload, Pkt)
  {
    const userTopics = ['forward','stock','outer']; // 策略可自主定于的主题分类白名单

    if (Path.devType == 'php')  // 前端通知
    {
      if (Path.func == 'policy') this.OnStateUpdeted(Payload.toString());
    }
    else if (Path.devType == 'srv') // 调试指令
    {
      if (Path.func == 'debug' && Payload.toString() == 'policy') {
        console.log('[PMR] ------------ DEBUG ------------');
        console.log('[PMR] Policys MAP:', PCY_MAP);
        console.log('[PMR] -------------------------------');
      } else if(Path.func == 'debug' && Payload.toString() == 'policy_topics') {
        console.log('[PMR] ------------ DEBUG ------------');
        console.log('[PMR] Policy Topics MAP:', PCY_TOPICS_MAP);
        console.log('[PMR] -------------------------------');
      }
    }
    else if (CFG.HwType.includes(Path.devType)) // for gw,wpass,wdev,dtu
    {
      this.dataMsgHandler(Path, Payload, Pkt);
    }
    else if (userTopics.includes(Path.devType)) // 用户自定义消息
    {
      this.userMsgHandler(Path, Payload, Pkt);
    }
    else
    {
      outErr('[PMR] Not support this topic type', Path);
    }
  } // end mqMsgIn

  // 用户消息处理
  static userMsgHandler(Path, Payload, Pkt)
  {
    let pcyArr = PCY_TOPICS_MAP.get(Pkt.topic);
    if (pcyArr == null) return;

    // 轮询订阅了的策略
    for (let i = 0; i < pcyArr.length; i++) {
      rCache.getAll(rCache.N_PolicyId, pcyArr[i].id).then((dConf)=>{
        if (dConf && dConf.policy) {
          const pInfo = PCY_MAP.get(parseInt(dConf.policy_id));
          const jsPath = path.join(PCY_DIR, dConf.policy+'.js');
          //if(typeof(pInfo)=='undefined') console.log('[PMR] map', optsCache.policy_id, policysMap);
          if (pInfo && pInfo.en) require(jsPath).DataIn(Path, dConf, Payload);
        }
      }) // end keys
    }
  }

  // 数据消息处理
  static dataMsgHandler(Path, Payload, Pkt)
  {
    if (Object.keys(Path).length != 4) return; // only 4 param

    let allowHandler = false;
    let devId = Path.devId;
    switch (Path.func) {
      case "tx":
      case "rx":
        allowHandler = true;
        break;
      case "var":
        if (Pkt.retain == true) break; // 不解析retain消息
        devId = devId.split('-')[1]; // 第一个设备可能为0，只对第二个设备id进行处理
        allowHandler = true;
        break;
    }

    if (!allowHandler) return;
    rCache.keys(rCache.N_Policy, devId+':*').then((policyList)=>{ // 寻找设备所有策略
      if (policyList == null) return;
      policyList.some((one)=>{
        rCache.getAll(one, '').then(dConf=>{ // 挨个获取单个策略信息
          // console.log('[PMR] policy options', optsCache);
          if (dConf && dConf.policy) {
            const pInfo = PCY_MAP.get(parseInt(dConf.policy_id));
            const jsPath = path.join(PCY_DIR, dConf.policy+'.js');
            //if(typeof(pInfo)=='undefined') console.log('[PMR] map', optsCache.policy_id, policysMap);
            if (pInfo && pInfo.en) require(jsPath).DataIn(Path, dConf, Payload);
          }
        }) // end getAll
      }) // end some
    }) // end keys
  }

  // 发出mq消息告诉前端
  static policyInform(cmd, dConf, logId=0)
  {
    if (FUN.isLocal()) { //不允许本地运行
      console.log('[PMR] N>> user/%d',dConf.uid,cmd,dConf.policy_id);
      return;
    }

    if (policyMan.mqPub) {
      let info = {};
      info.cmd = cmd;
      info.policy = dConf.policy_id;
      info.log = logId;
      policyMan.mqPub(`user/${dConf.uid}`, JSON.stringify(info));
    }
  }

  // 为策略设定循环定时器
  static setSecInter(policy_id, periodSec, delay = 0)
  {
    let pInfo = PCY_MAP.get(parseInt(policy_id));
    if (pInfo) {
      if (periodSec <= 0) pInfo.msGap = 0;
      else pInfo.msGap = periodSec*1000;
      pInfo.lastExpire = timLib.NowStamp() + delay*1000;
      PCY_MAP.set(parseInt(policy_id), pInfo);
      // console.log(`[PMR] [${policy_id}] Set Sec Interrpt: ${pInfo.msGap} Ms`);
    }
  }

  // 请求消息接口
  // id: 数据库id
  // dConf必须有policy_id, only_once, hwid
  static alarmReport(id, dConf)
  {
    if(FUN.isLocal()) { // 不允许本地运行
      console.log('[PMR] N>> alarm[%d]',id,dConf.policy_id);
      return;
    }

    request({
      url: CFG.WebReportHost+'/index/api/alarmReport',
      method: "POST", json: true,
      headers: {"content-type":"application/json"},
      form: {
        id:id, pid:dConf.policy_id,
        type:dConf.only_once == 'true' ? 0 : 1,
        key:'D95F3A52E56D5C6E3CFF37C60D647254'
      }
    }, (error, response, body)=>{
      if (!error && response.statusCode == 200) {
        // console.log('[PMR]', body) //请求成功的处理逻辑
        policyMan.policyInform('alarm',dConf,id);//实时通知前端报警
        policyMan.policyInform('update',dConf);//实时通知前端更新图表
      } else {
        console.log('[PMR] [%d][%d] alram faild.',dConf.policy_id,dConf.hwid);
      }
    }) // end request
  } // end alarmReport

}//end module class